unit CMDUnit;

interface

uses System.SysUtils, System.Classes, winapi.Windows;

type
    TADBCmdResult = class(TPersistent)
    private
        fResultStr: String;
        fLines: TStrings;
        procedure setResultStr(const Value: String);
    protected
        procedure analyse; virtual;
    public
        constructor Create(); overload; virtual;
        constructor Create(ResultStr: String); overload; virtual;
        destructor Destroy; override;
        Property ResultStr: String read fResultStr write setResultStr;
        property Lines: TStrings read fLines;
    end;

    TAndroidDevice = class(TPersistent)
    private
        fDeviceName: String;
        fDeviceId: String;
        procedure setDeviceId(const Value: String);
        procedure setDeviceName(const Value: String);
    public
        property DeviceId: String read fDeviceId write setDeviceId;
        property DeviceName: String read fDeviceName write setDeviceName;
    end;

    TADBDeviceListResult = class(TADBCmdResult)
    private
        fDeviceList: TList;
        procedure clear;
        function GetDevice(index: integer): TAndroidDevice;
        procedure SetDevice(index: integer; const Value: TAndroidDevice);
    protected
        procedure analyse; override;
    public
        constructor Create(); overload; override;
        constructor Create(ResultStr: String); overload; override;
        destructor Destroy; override;
        procedure refresh;
        property DeviceList: TList read fDeviceList;
        property Devices[index: integer]: TAndroidDevice read GetDevice
          write SetDevice;
    end;

function RunDosCommand(ExePath: String; CommandLine: string): string;

implementation

procedure CheckResult(b: Boolean);
begin
    if not b then
        Raise Exception.Create(SysErrorMessage(GetLastError));
end;

function RunDosCommand(ExePath: String; CommandLine: String): String;
var
    hReadPipe: THandle;
    hWritePipe: THandle;
    SI: TStartUpInfo;
    PI: TProcessInformation;
    SA: TSecurityAttributes;
    SD: TSecurityDescriptor;
    BytesRead: DWORD;
    Dest: AnsiString;
    TmpList: TStringList;
    Avail, ExitCode, wrResult: DWORD;
    osVer: TOSVERSIONINFO;
    tmpstr: AnsiString;

begin
    SetLength(Dest, 1024);
    osVer.dwOSVersionInfoSize := Sizeof(TOSVERSIONINFO);
    GetVersionEX(osVer);

    if osVer.dwPlatformId = VER_PLATFORM_WIN32_NT then
    begin
        InitializeSecurityDescriptor(@SD,   SECURITY_DESCRIPTOR_REVISION);
        SetSecurityDescriptorDacl(@SD,   True,   nil,   False);
        SA.nLength := Sizeof(SA);
        SA.lpSecurityDescriptor := @SD;
        SA.bInheritHandle := True;
        CreatePipe(hReadPipe, hWritePipe, @SA, 0);
    end
    else
        CreatePipe(hReadPipe, hWritePipe, nil, 1024);
    try
        FillChar(SI, Sizeof(SI), 0);
        SI.cb := Sizeof(TStartUpInfo);
        SI.wShowWindow := SW_HIDE;
        SI.dwFlags := STARTF_USESHOWWINDOW;
        SI.dwFlags := SI.dwFlags or STARTF_USESTDHANDLES;
        SI.hStdOutput := hWritePipe;
        SI.hStdError := hWritePipe;
        if CreateProcess(PChar(ExePath), PChar(CommandLine), nil, nil, True,
          NORMAL_PRIORITY_CLASS, nil, nil, SI, PI) then
        begin
            ExitCode := 0;
            while ExitCode = 0 do
            begin
                wrResult := WaitForSingleObject(PI.hProcess, 156500);
                if PeekNamedPipe(hReadPipe, @Dest[1], 1024, @Avail, nil, nil)
                then
                begin
                    if Avail > 0 then
                    begin
                        TmpList := TStringList.Create;
                        try
                            FillChar(Dest[1], Length(Dest) * Sizeof(Char), 0);
                            ReadFile(hReadPipe, Dest[1], Avail, BytesRead, nil);
                            tmpstr := Copy(Dest, 0, BytesRead - 1);
                            TmpList.Text := tmpstr;
                            Result := tmpstr;
                        finally
                            TmpList.Free;
                        end;
                    end;
                end;
                if wrResult <> WAIT_TIMEOUT then
                    ExitCode := 1;
            end;
            GetExitCodeProcess(PI.hProcess, ExitCode);
            CloseHandle(PI.hProcess);
            CloseHandle(PI.hThread);
        end;
    finally
        CloseHandle(hReadPipe);
        CloseHandle(hWritePipe);
    end;
end;

{ TADBCmdResult }

constructor TADBCmdResult.Create;
begin
    fLines := TStringList.Create;
end;

procedure TADBCmdResult.analyse;
var
    tmpLines: TArray<String>;
    I: integer;
begin
    tmpLines := fResultStr.Split([#13#10]);
    fLines.clear;
    for I := Low(tmpLines) to High(tmpLines) do
    begin
        fLines.Append(tmpLines[I]);
    end;
    SetLength(tmpLines, 0);
end;

constructor TADBCmdResult.Create(ResultStr: String);
begin
    fLines := TStringList.Create;
    setResultStr(ResultStr);
end;

destructor TADBCmdResult.Destroy;
begin
    fLines.Free;
    inherited;
end;

procedure TADBCmdResult.setResultStr(const Value: String);
var
    tmpLines: TArray<String>;
    I: integer;
begin
    if (CompareStr(fResultStr, Value) < 1) then
    begin
        fResultStr := Value;
        analyse;
    end;
end;

{ TADBDeviceListResult }

procedure TADBDeviceListResult.analyse;
var
    I: integer;
    tmpstr: String;
    deviceInfo: TArray<String>;
    Device: TAndroidDevice;
begin
    inherited analyse;
    if fLines.Count > 1 then
    begin
        for I := 1 to fLines.Count - 1 do
        begin
            tmpstr := fLines[I];
            deviceInfo := tmpstr.Split([#9]);
            if Length(deviceInfo) > 1 then
            begin
                Device := TAndroidDevice.Create();
                Device.fDeviceId := deviceInfo[0].Trim;
                Device.fDeviceName := deviceInfo[1].Trim;
                fDeviceList.Add(Device);
            end;
            SetLength(deviceInfo, 0);
        end;
    end;
end;

procedure TADBDeviceListResult.clear;
var
    I: integer;
begin
    for I := 0 to fDeviceList.Count - 1 do
    begin
        TAndroidDevice(fDeviceList.Items[I]).Free;
    end;
    fDeviceList.clear;
end;

constructor TADBDeviceListResult.Create(ResultStr: String);
begin
    fDeviceList := TList.Create;
    inherited Create(ResultStr);
end;

constructor TADBDeviceListResult.Create;
begin
    fDeviceList := TList.Create;
    inherited Create;
end;

destructor TADBDeviceListResult.Destroy;
begin
    clear;
    fDeviceList.Free;
    inherited;
end;

function TADBDeviceListResult.GetDevice(index: integer): TAndroidDevice;
begin
    Result := TAndroidDevice(fDeviceList.Items[Index]);
end;

procedure TADBDeviceListResult.refresh;
begin
    fResultStr := RunDosCommand(ExtractFilePath(ParamStr(0)) + 'adb.exe',
      ' devices');
    analyse;
end;

procedure TADBDeviceListResult.SetDevice(index: integer;
  const Value: TAndroidDevice);
begin
    if index < fDeviceList.Count then
    begin
        fDeviceList.Items[index] := Value;
    end
    else
    begin
        fDeviceList.Add(Value);
    end;
end;

{ TAndroidDevice }

procedure TAndroidDevice.setDeviceId(const Value: String);
begin
    fDeviceId := Value;
end;

procedure TAndroidDevice.setDeviceName(const Value: String);
begin
    fDeviceName := Value;
end;

end.
